package com.example.demo;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;

/**
 * 报文包处理
 *
 * @author haiyang.li
 */
public class PackageWrapper {

    private byte[] source;//8 消息源标识
    private PacketType type;//1 通讯包类型
    private PacketCipherMode cipherMode;//1 消息体加密类型
    private PacketPattern pattern;//1 消息体类型
    private PacketFormat format;//1 消息体格式 json xml
    private PacketEncoding encoding;//1 消息体编码
    private int commandWord;//4 命令字
    private int size;//4 报文体长度
    private byte[] body;//21+size 报文体


    public static void main(String[] args) {
        String body = "{\"id\":\"00AYS2022102700012\",\"message\":\"心跳检测\"}";
        //封包
        PackageWrapper pack = pack(body);
        System.out.println(pack.toString());
        //获取完整报文byte
        byte[] packBytes = pack.getBytes();
        //base64 编码
        String encode = Base64.getEncoder().encodeToString(packBytes);
        System.out.println(encode);

        

        //base64 解码
        byte[] decode = Base64.getDecoder().decode(encode);
        //拆包
        PackageWrapper unpack = unpack(decode);
        System.out.println(unpack);
        //根据报文头 获取报文体
        String packBody = unpack.getPackBody();
        System.out.println(packBody);
    }

    public static PackageWrapper unpack(byte[] pack) {
        PackageWrapper wrapper = new PackageWrapper();
        ByteBuffer buffer = ByteBuffer.wrap(pack);
        byte[] source = new byte[8];
        buffer.get(source, 0, 8);
        wrapper.setSource(source);
        wrapper.setType(PacketType.valueOf(buffer.get()));
        wrapper.setCipherMode(PacketCipherMode.valueOf(buffer.get()));
        wrapper.setPattern(PacketPattern.valueOf(buffer.get()));
        wrapper.setFormat(PacketFormat.valueOf(buffer.get()));
        wrapper.setEncoding(PacketEncoding.valueOf(buffer.get()));
        wrapper.setCommandWord(buffer.getInt());
        int size = buffer.getInt();
        wrapper.setSize(size);
        byte[] body = new byte[size];
        buffer.get(body, 0, size);
        wrapper.setBody(body);
        return wrapper;
    }

    public String getPackBody() {
        byte[] buffer = this.body;
        //根据报文头的加解密方式 先解密
        if (PacketCipherMode.NONE.equals(this.cipherMode)) {

        } else if (PacketCipherMode.DES.equals(this.cipherMode)) {
            //des 解密
            buffer = des(buffer);
        } else if (PacketCipherMode.DES3.equals(this.cipherMode)) {
            //des3 解密
            buffer = des3(buffer);
        } else if (PacketCipherMode.SM4.equals(this.cipherMode)) {
            //sm4 解密
            buffer = sm4(buffer);
        }

        try {
            //再根据报文头的编码方式，转换对应的字符串
            return new String(buffer, this.encoding.getCharset());
        } catch (UnsupportedEncodingException cause) {
            throw new RuntimeException(String.format("响应内容为不支持字符集 '%s' ", this.encoding.getCharset()));
        }
    }

    //封包
    public static PackageWrapper pack(String body) {
        PackageWrapper wrapper = new PackageWrapper();
        //设置约定报文头配置信息
        wrapper.setSource("FUNI$UNI".getBytes(StandardCharsets.US_ASCII));
        wrapper.setType(PacketType.REQUEST);
        wrapper.setCipherMode(PacketCipherMode.NONE);
        wrapper.setPattern(PacketPattern.NORMAL);
        wrapper.setFormat(PacketFormat.JSON);
        wrapper.setEncoding(PacketEncoding.UTF8);
        wrapper.setCommandWord(0x6001);

        //根据报文头编码 获取报文体的byte
        byte[] bodyBytes = body.getBytes(Charset.forName(wrapper.getEncoding().getCharset()));
        //根据报文头加密方式，对报文体进行加密操作
        if (PacketCipherMode.NONE.equals(wrapper.getCipherMode())) {

        } else if (PacketCipherMode.DES.equals(wrapper.getCipherMode())) {
            // des 加密
            bodyBytes = des(bodyBytes);
        } else if (PacketCipherMode.DES3.equals(wrapper.getCipherMode())) {
            // des3 加密
            bodyBytes = des3(bodyBytes);
        } else if (PacketCipherMode.SM4.equals(wrapper.getCipherMode())) {
            // sm4 加密
            bodyBytes = sm4(bodyBytes);
        }
        //设置报文体的长度
        wrapper.setSize(bodyBytes.length);
        //设置报文体内容
        wrapper.setBody(bodyBytes);
        return wrapper;
    }

    public byte[] getBytes() {
        ByteBuffer buffer = ByteBuffer.allocate(21 + this.size);
        buffer.put(this.source);
        buffer.put(this.type.getValue());
        buffer.put(this.cipherMode.getValue());
        buffer.put(this.pattern.getValue());
        buffer.put(this.format.getValue());
        buffer.put(this.encoding.getValue());
        buffer.putInt(this.commandWord);
        buffer.putInt(this.size);
        buffer.put(this.body);
        return buffer.array();
    }

    private static byte[] des(byte[] bytes) {
        //加解密实现
        return bytes;
    }

    private static byte[] des3(byte[] bytes) {
        //加解密实现
        return bytes;
    }

    private static byte[] sm4(byte[] bytes) {
        //加解密实现
        return bytes;
    }


    @Override
    public String toString() {
        return "PackageWrapper{" +
                "source=" + new String(source, StandardCharsets.US_ASCII) +
                ", type=" + type +
                ", cipherMode=" + cipherMode +
                ", pattern=" + pattern +
                ", format=" + format +
                ", encoding=" + encoding +
                ", commandWord=" + "0x" + Integer.toHexString(commandWord) +
                ", size=" + size +
                ", body=" + Arrays.toString(body) +
                '}';
    }

    public enum PacketType {
        REQUEST(0x01),
        RESPONSE(0x02);
        private byte value;

        PacketType(int value) {
            this.value = (byte) value;
        }

        public byte getValue() {
            return value;
        }

        public static PacketType valueOf(byte value) {
            for (PacketType type : values()) {
                if (type.value == value) {
                    return type;
                }
            }
            throw new RuntimeException(String.format("错误的数据包模式 0x%04X ", value));
        }
    }

    public enum PacketCipherMode {
        NONE(0x01),
        DES(0x02),
        DES3(0x03),
        SM4(0x04);
        private final byte value;

        PacketCipherMode(int value) {
            this.value = (byte) value;
        }

        public byte getValue() {
            return value;
        }

        public static PacketCipherMode valueOf(byte value) {
            for (PacketCipherMode mode : values()) {
                if (mode.value == value) {
                    return mode;
                }
            }
            throw new RuntimeException(String.format("错误的数据包加密方式 0x%04X ", value));
        }
    }

    public enum PacketPattern {
        NORMAL(0x01),//正常包
        ERROR(0x02),//错误包
        COMPLETED(0x03);//请求已完成,实现重发机制
        private final byte value;

        PacketPattern(int value) {
            this.value = (byte) value;
        }

        public byte getValue() {
            return value;
        }

        public static PacketPattern valueOf(byte value) {
            for (PacketPattern pattern : values()) {
                if (pattern.value == value) {
                    return pattern;
                }
            }
            throw new RuntimeException(String.format("错误的数据包类型 0x%04X ", value));
        }
    }

    public enum PacketFormat {
        XML(0x01),
        JSON(0x02);
        private final byte value;

        PacketFormat(int value) {
            this.value = (byte) value;
        }

        public byte getValue() {
            return value;
        }

        public static PacketFormat valueOf(byte value) {
            for (PacketFormat format : values()) {
                if (format.value == value) {
                    return format;
                }
            }
            throw new RuntimeException(String.format("错误的数据包格式 0x%04X ", value));
        }
    }

    public enum PacketEncoding {
        ASCII(0x01, "ascii"),
        UTF8(0x02, "utf-8"),
        GBK(0x03, "gbk");
        private final byte value;
        private final String charset;

        PacketEncoding(int value, String charset) {
            this.value = (byte) value;
            this.charset = charset;
        }

        public byte getValue() {
            return value;
        }

        public String getCharset() {
            return this.charset;
        }

        public static PacketEncoding valueOf(byte value) {
            for (PacketEncoding encoding : PacketEncoding.values()) {
                if (encoding.value == value) {
                    return encoding;
                }
            }
            throw new RuntimeException(String.format("错误的数据包字符编码类型 0x%04X ", value));
        }
    }


    public byte[] getSource() {
        return source;
    }

    public void setSource(byte[] source) {
        this.source = source;
    }

    public PacketType getType() {
        return type;
    }

    public void setType(PacketType type) {
        this.type = type;
    }

    public PacketCipherMode getCipherMode() {
        return cipherMode;
    }

    public void setCipherMode(PacketCipherMode cipherMode) {
        this.cipherMode = cipherMode;
    }

    public PacketPattern getPattern() {
        return pattern;
    }

    public void setPattern(PacketPattern pattern) {
        this.pattern = pattern;
    }

    public PacketFormat getFormat() {
        return format;
    }

    public void setFormat(PacketFormat format) {
        this.format = format;
    }

    public PacketEncoding getEncoding() {
        return encoding;
    }

    public void setEncoding(PacketEncoding encoding) {
        this.encoding = encoding;
    }

    public int getCommandWord() {
        return commandWord;
    }

    public void setCommandWord(int commandWord) {
        this.commandWord = commandWord;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public byte[] getBody() {
        return body;
    }

    public void setBody(byte[] body) {
        this.body = body;
    }
}
